package pl.edu.mimuw.irs.core.model.analysis.dd;

import java.util.Collection;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Map.Entry;

import pl.edu.mimuw.irs.core.model.analysis.ResourcePlace;
import pl.edu.mimuw.irs.core.model.design.Fragmentation;
import pl.edu.mimuw.irs.core.model.design.processing.Requirement;

public class PathNode {

	// *** Attributes

	private Fragmentation fragmentation;
	private ActivityWrapper activity;
	/*
	 * Owner is only in the root path node. As activity it is not stored in
	 * activity because activities are stored only in leaves, and whole DD
	 * mechanism bases on this assumption.
	 */
	private ActivityWrapper owner;
	private PathNode root;
	private PathNode parent;
	private List<PathNode> children;
	private int depth;
	private boolean deadEnd;
	private boolean started;
	private Boolean cachedValue;
	// Getters and setters;
	public Fragmentation getFragmentation() {
		return fragmentation;
	}
	public ActivityWrapper getActivity() {
		return activity;
	}
	public PathNode getRoot() {
		return root;
	}
	public void setRoot(PathNode root) {
		this.root = root;
		if (getParent() != null) {
			this.depth = getParent().getDepth() + 1;
		} else {
			this.depth = 0;
		}
		for (PathNode child : getChildren()) {
			child.setRoot(root);
		}
	}
	public PathNode getParent() {
		return parent;
	}
	public void setParent(PathNode parent) {
		this.parent = parent;
		if (parent == null) {
			this.depth = 0;
			setRoot(this);
		} else {
			parent.getChildren().add(this);
			setRoot(parent.getRoot());
		}
	}
	public List<PathNode> getChildren() {
		if (activity != null && activity.getDescendingPath() != null)
				return (activity.getDescendingPath().getChildren());
		if (children == null) {
			children = new LinkedList<PathNode>();
		}
		return children;
	}
	public int getDepth() {
		return depth;
	}
	public boolean isDeadEnd() {
		return deadEnd && activity == null;
	}
	public void setDeadEnd(boolean deadEnd) {
		this.deadEnd = deadEnd;
	}
	public boolean isStarted() {
		return started;
	}
	public void setStarted(boolean started) {
		this.started = started;
		if (getParent() != null) getParent().setStarted(started);
	}
	public ActivityWrapper getOwner() {
		return owner;
	}
	public void setOwner(ActivityWrapper owner) {
		this.owner = owner;
	}
	public Boolean getCachedValue() {
		return cachedValue;
	}
	public void clearCache() {
		cachedValue = null;
		if (parent != null) parent.clearCache();
		if (owner != null) owner.clearCache();
	}
	
	// *** Constructors
	
	public PathNode(PathNode parent, Fragmentation fragmentation, List<PathNode> children, ActivityWrapper activity) {
		this.fragmentation = fragmentation;
		this.children = children;
		this.activity = activity;
		setParent(parent);
	}
	
	public PathNode(PathNode parent, Fragmentation fragmentation, ActivityWrapper activity) {
		this(parent, fragmentation, null, activity);
	}
	
	public PathNode(PathNode parent, Fragmentation fragmentation) {
		this(parent, fragmentation, (ActivityWrapper)null);
	}

	public PathNode(PathNode parent, Fragmentation fragmentation, List<PathNode> children) {
		this(parent, fragmentation, children, null);
	}

	public PathNode(Fragmentation fragmentation, List<PathNode> children) {
		this(null, fragmentation, children, null);
	}
	
	public PathNode(Fragmentation fragmentation) {
		this(null, fragmentation);
	}
	
	// *** Methods
	
	@Override
	public String toString() {
		if (getActivity() == null) {
			if (deadEnd) {
				return "DeadEnd";
			} else {
				return "" + getFragmentation() + getChildren();
			}
		} else {
			return getActivity().toString();
		}
	}
	
	public boolean canBeCompleted(Map<Requirement, Dependency> nodes, boolean eager) {
		/*
		 * The eagerness is used, when stack is not null.
		 */
		
		if (eager) {
			return canBeCompleted(nodes, new LinkedHashSet<ActivityWrapper>());
		} else {
			return canBeCompleted(nodes, null);
		}
		/*
		 * TODO : add caching
		 */
	}
	
	private boolean canBeCompleted(Map<Requirement, Dependency> nodes, Set<ActivityWrapper> stack) {
		/*
		 * Try cached value.
		 */
		if (getCachedValue() != null) return getCachedValue();
		/*
		 * If we are eager, we have to check if we are not looping.
		 */
		if (stack != null && getActivity() != null) {
			if (stack.contains(getActivity())) return false;
			else stack.add(getActivity());
		}
		/*
		 * We treat CONCURRENCY and SEQUENCE as AND operator, and
		 * ALTERNATIVE as OR operator. TODO : ITERATION.
		 * The eagerness is used, when stack is not null.
		 */
		if (canBeStarted(nodes, stack)) {
			boolean result;
			switch (getFragmentation().getType()) {
			case CONCURRENCY:
			case SEQUENCE:
				result = true;
				for (PathNode child : getChildren()) {
					if (!child.isDeadEnd()) {
						result = result && child.canBeCompleted(nodes, stack);
					}
				}
				break;
			case ALTERNATIVE:
				result = false;
				boolean isStarted;
				/* 
				 * If this node is already started we need to find which way has
				 * been chosen and check that one. Otherwise we can find any possible 
				 * way. If we are in activity, then we need to use a trick to find
				 * out if this is the last started activity or any further descendants
				 * are started as well.
				 */
				if (this.getActivity() != null) {
					if (getActivity().getDescendingPath().isStarted()) {
						/* Some child is started. */
						isStarted = true;
					} else {
						/* There is no started child. */
						isStarted = false;
					}
				} else {
					if (isStarted()) {
						/* Some child is started. */
						isStarted = true;
					} else {
						/* There is no started child. */
						isStarted = false;
					}
				}
				
				if (isStarted) {
					/* 
					 * We must find out which child is started and follow the choice.
					 */
					for (PathNode child : getChildren()) {
						if (child.isStarted()) {
							result = child.canBeCompleted(nodes, stack);
							break;
						}
					}
				} else {
					/* 
					 * We can choose any way we like.
					 */
					for (PathNode child : getChildren()) {
						if (child.isDeadEnd()) {
							result = true;
							break;
						} else if (child.canBeCompleted(nodes, stack)) {
							result = true;
							break;
						}
					}
				}
				break;
			default:
				// TODO : ITERATION
				result = true;
				break;
			}
			return result;
		} else {
			return false;
		}
	}
	
	private boolean canBeStarted(Map<Requirement, Dependency> nodes, Set<ActivityWrapper> stack) {
		if (getActivity() != null) {
			/*
			 * First - if activity has already started than yes - it can be started.
			 */
			if (getActivity().isStarted()) return true;
			/*
			 * Now we need to get requirements in token-like presentation.
			 */
			Map<Requirement, Integer> map = getActivity().getActivity().
					getRequirement().getRequirementsMap();
			/*
			 * Now we can test if all requirements are available.
			 */
			for (Entry<Requirement, Integer> entry : map.entrySet()) {
				Dependency dependency = nodes.get(entry.getKey());
				int diff = dependency.getRequirementPlace().getTokens() - entry.getValue();
				if (!(diff >= 0)) {
					/*
					 * This is where eagerness comes to action - if available resources are 
					 * not enough, we ask other activities, that has already taken some, 
					 * if they are able to finish and give us them back later. The eagerness
					 * is used, when stack is not null. 
					 */
					if (stack != null) {
						Set<ActivityWrapper> working = dependency.getWorkingActivities();
						for (ActivityWrapper wrapper : working) {
							if (wrapper.getDescendingPath().canBeCompleted(nodes, stack)) {
								diff += wrapper.getActivity().getRequirement().
										getRequirementsMap().get(dependency.getRequirement());
								if (diff >= 0) return true;
								/* TODO : Requirements places must be created and correctly handled!
								 * TODO : if parent requested this requirement we have to take it
								 * under consideration as well
								 */
							}
						}
						return false;
					} else {
						return false;
					}
				}
			}
			
			return true;
		} else {
			return true;
		}
	}
	
	public Collection<? extends ResourcePlace> getDerivedResources() {
		if (owner != null) return owner.getDerivedResources();
		else if (parent != null) return parent.getDerivedResources();
		else return new HashSet<ResourcePlace>();
	}
}
